package edu.psu.geovista.app.pcp.plot.control.tools;

import edu.psu.geovista.app.pcp.plot.gui.BasicPcpUI;
import edu.psu.geovista.app.pcp.plot.layer.AxisLayer;
import edu.psu.geovista.app.pcp.plot.layer.DataLayer;
import edu.psu.geovista.app.pcp.toolkits.collection.CollectionUtils;
import edu.psu.geovista.app.pcp.toolkits.group.DataIDSet;
import edu.psu.geovista.app.plot.axis.AxisList;
import edu.psu.geovista.app.plot.axis.BoxPlot;
import edu.psu.geovista.app.plot.axis.Range;
import edu.psu.geovista.app.plot.axis.YAxis;
import edu.psu.geovista.app.plot.layer.Layer;
import edu.psu.geovista.classification.CategoryList;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;

/**
 * Created by IntelliJ IDEA.
 * User: jinchen
 * Date: Jun 27, 2003
 * Time: 10:06:09 PM
 * To change this template use Options | File Templates.
 */
public class AxisShowRangeTool extends BasicMouseTool {
    //protected Object[] activeAxisNames;
    protected YAxis[] activeAxisGroup; //axises whose value is to be changed
    //protected AppDataModel dataModel;
    public AxisShowRangeTool(BasicPcpUI ui) {
        super(ui);
        //dataModel=this.getUI().getPlot().getDataModel();
    }
    /*******************************************************************************************************
     *                         Service methods
     *******************************************************************************************************/
    public void updateTickRange(Range range) {
          this.updateAxisTickRange(range);
          this.updateLayerForScale() ;
    }
    public void updateShowRange(Range range) {
           this.updateAxisShowRange(range);
           this.updateLayerForShowRange() ;
    }
    /**
     * reset showRange and tickRange to equal to dataRange  for all axis
     */
    public void resetRanges() {
        Iterator iter=getAxisList().iterator() ;
        while(iter.hasNext()){
           YAxis axis = (YAxis)iter.next();
           Range dataR=axis.getDataRange() ;
           axis.setTickRange(dataR);
           axis.setShowRange(dataR);

        }
        this.repaintAxisLayer() ;
        this.updateLayerForShowRange(); //show range layer
        this.repaintDataLayer() ; //background layer
        this.repaintPlot() ;

        //this.ui.get


    }

    //The method can be put in any AxisCmd. The purpose is to repaint axisLayer
    public void updateCategoryAxis(String name, CategoryList ctgList) {
        AxisList axisList=getAxisList();
        YAxis axisCtg=axisList.getCtgAxis();
        //if (axisCtg.getName().equals(name) ) return;
        //axisCtg=(YAxis) axisList.get(name);
        axisList.setCtgAxis(name,ctgList) ;
        repaintAxisLayer();


    }
    public void setActiveAxisGroup(YAxis[] activeAxisGroup) {
        this.activeAxisGroup = activeAxisGroup;
    }

    public void showCorrelation() {
        AxisLayer layer=(AxisLayer) this.getLayer(Layer.AXIS_LAYER);
        boolean showCorrelation=layer.isShowCorrelation() ;
        layer.setShowCorrelation(!showCorrelation);
        layer.repaintLayer();
        this.repaintPlot();
    }
    /*******************************************************************************************************
     *                move up down axis
     *******************************************************************************************************/
    public void mousePressed(MouseEvent e) {
         this.updateLCpoint(e.getPoint()) ;
        this.setActiveAxis(findActiveAxis(cp.x,cp.y));
         /*if (! this.isToolOn() ){
            this.setActiveAxis(findActiveAxis(cp.x,cp.y));
         }*/
         if (activeAxis!=null){
             this.setToolOn(true);
         }
         //dp{
         if (DEBUG){
             System.out.println("mouse press on ");
         }//dp}
     }
    public void mouseDragged(MouseEvent e) {
         this.updateLCpoint(e.getPoint()) ;
         Point cp=e.getPoint() ;
         int deltay=cp.y-this.lastp.y ;
         if(this.activeAxis !=null){
         	 float lastv=activeAxis.getValueOf(lastp.y);
         	 float curv=activeAxis.getValueOf(cp.y);
         	 float dy=curv-lastv;
             //float dy=activeAxis.getValueOfchange(deltay);
             
             activeAxisGroup=new YAxis[]{activeAxis};
             Range oldTR=activeAxis.getTickRange() ;
             Range tickR=new Range(oldTR.getMax() +dy,oldTR.getMin()+dy);
             this.updateTickRange(tickR);
         }
       


     }

      public void mouseReleased(MouseEvent e) {

         this.setActiveAxis(null);

           super.mouseReleased(e);
      }

    /*******************************************************************************************************
     *                update Axises' value
     *******************************************************************************************************/

    /**
     *
     * @return       activeAxis' index
     */
    private  int[] getIndexOfActiveAxisGroup() {
       int[] aai=new int[this.activeAxisGroup.length ];//activeAxisIndex
       for (int j=0;j<aai.length ;j++){
                   YAxis ax=activeAxisGroup[j];
            aai[j]=ax.getIndex() ;
        }
        return aai;
    }


    /**
     * make active axises's scale value = that of axis
     * @param range  tickRange
     */
    private  void updateAxisTickRange(Range range) {
        for (int i=0;i<activeAxisGroup.length ;i++){
            //Extreme r=this.getMeaningFullRange(activeAxisGroup[i],range);
            activeAxisGroup[i].setTickRange(range );
        }
    }
    /**
     *  make active axises's show range value = that of axis
     * @param range showRange
     */
    private  void updateAxisShowRange(Range range){
       for (int i=0;i<activeAxisGroup.length ;i++){
            //Extreme r=this.getMeaningFullRange(activeAxisGroup[i],range);
            activeAxisGroup[i].setShowRange( range );
        }
    }
    /**
     * We assume axis's showRange and tickRange are within its dataRange.
     * Otherwise the corresponding handlers in Configuring Axis(MTYAxis) may go out of sight,
     * making the range out of control
     *
     * To make a Configuring Axis able to configure multiple axis concurrently(by multiple selection)
     * a given range is meaningful to a axis only when it values(max, min) are within the axis's dataRange
     * @param ax
     * @param range
     * @return  the Joint union of given range and axis's dataRange
     */
    private Range getMeaningFullRange(YAxis ax, Range range){
        Range dataR=ax.getDataRange() ;
        float min=Math.max(dataR.getMin() ,range.getMin() );
        float max=Math.min(dataR.getMax() ,range.getMax() );
        return new Range(max,min);

    }
    /*******************************************************************************************************
     *                update layers for change on axis's scale
     *******************************************************************************************************/
    private  void updateLayerForScale() {
        //this.activeAxis.setTickRange(range);
        //System.out.println("axis:"+this.activeAxis.getName() );
        repaintAxisLayer();

        Object[] dataLys=this.getLayerByType(Layer.TYPE_DATA );
        for (int i=0;i<dataLys.length ;i++){
            DataLayer dataLayer=(DataLayer) dataLys[i];
            if(dataLayer.isVisible() ) {
               dataLayer.repaintAxis(this.getIndexOfActiveAxisGroup() );
            }
        }

        this.repaintPlot() ;



    }
    /*******************************************************************************************************
     *              pdate layers for change on axis's show range
     *******************************************************************************************************/

    private  void updateLayerForShowRange() {
        // 
        repaintAxisLayer(); //update showRange tick on axis
        this.repaintPlot() ;
        // 
        SwingUtilities.invokeLater(
        		new Runnable()
        		{
        		public void run()
        		{
                    DataLayer dataLayer=(DataLayer)ui.getLayer(Layer.BgData_LAYER );
                    if(dataLayer!=null)
                        dataLayer.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER ,DataLayer.DataBackgroundLightness) );

                    DataLayer srLayer=(DataLayer)ui.getLayer(Layer.TopData_LAYER );
                    //int[] ids=this.getRecordsByShowRange();
                    AxisList yaxisList=getUI().getPlot().getYaxisList() ;
                    int[] ids=getService().getRecordsByShowRange(yaxisList);


                    srLayer.setRecordsToShow(new DataIDSet(ids,DataIDSet.TYPE_AxisShowRangeData ) );
                    srLayer.setVisible(true);
                    srLayer.clearLayer() ; //MUST
                    srLayer.paintLayer() ;
                    repaintPlot() ;

                    HashSet Ids = CollectionUtils.convertArrayToSet(ids);
                    DataSelectTool selTool = (DataSelectTool) getCmd(BasicTool.DataSelectT);
                    selTool.fireSelectionChange(Ids);
                }
        		});


    }


    /*******************************************************************************************************
     *                BoxPlot
     *******************************************************************************************************/
    public void reverseShowBoxPlot() {
        Enumeration e=this.getAxisList().elements() ;
        while(e.hasMoreElements() ){
            YAxis axis=(YAxis) e.nextElement() ;
            axis.setBoxPlotShown(!axis.isBoxPlotShown() );

        }
        this.repaintAxisLayer() ;
        this.repaintPlot() ;
    }
    // align all axis to make the median value of boxplot in the middle of the axis
    public void alignBoxPlot() {
       Enumeration e=this.getAxisList().elements() ;
        while(e.hasMoreElements() ){
            YAxis axis=(YAxis) e.nextElement() ;
            Range tickR=this.getTickRangeForAlignment(axis);
            if(tickR==null)continue;//there is no box plot
            axis.setTickRange(tickR);
            axis.setBoxPlotShown(true );
        }
        this.repaintDataLayer() ;
        this.repaintAxisLayer() ;
        this.repaintPlot() ;
    }

    private Range getTickRangeForAlignment(YAxis axis){
        BoxPlot box=axis.getBoxplot() ;
        if(box==null)return null;
        float median=box.getMedian() ;
        float max=axis.getDataRange().getMax() ;
        float min=axis.getDataRange().getMin() ;
        float farExtreme;//the extreme value of datarange that is further to median
        float secondExtreme;
        if(max-median>=median-min){
            farExtreme=max;
            secondExtreme=2*median-farExtreme;
            return new Range(farExtreme,secondExtreme);
        }
        else{
            farExtreme=min;
            secondExtreme=2*median-farExtreme;
            return new Range(secondExtreme,farExtreme);
        }


    }
    /*******************************************************************************************************
    *                support methods
    *******************************************************************************************************/


    private void repaintAxisLayer() {
         //to update showRange tick on the axis
        AxisLayer axisLayer=(AxisLayer)this.ui.getLayer(Layer.AXIS_LAYER );
        axisLayer.repaintLayer( );
    }
    private void repaintDataLayer() {
        Object[] dataLys=this.getLayerByType(Layer.TYPE_DATA );
        for (int i=0;i<dataLys.length ;i++){
            DataLayer dataLayer=(DataLayer) dataLys[i];
            if(dataLayer.isVisible() ) {

                dataLayer.clearLayer() ;
                dataLayer.paintLayer() ;
            }
        }
    }

   /* public void setDataModel(AppDataModel dataModel) {
        this.dataModel = dataModel;
    }*/


}
